package dbx

import (
	"fmt"
	"gitee.com/zhongguo168a/gocodes/myx/errorx"
	"github.com/oxequa/grace"
)

type AfterHandler struct {
	handler func(ctx *AfterHandler)
	args    []interface{}
	reason  error
}

func (ctx *AfterHandler) Interrupt(reason error) {
	ctx.reason = reason
}
func (delayQueue *DelayQueue) Commit() (err error) {
	if delayQueue.isExpired {
		panic("delay is expired")
	}
	if databaseException {
		return ErrDatabaseException
	}
	//fmt.Printf(">>> Commit %v| items=%v\n", delayQueue.saveKey, len(delayQueue.items))
	defer grace.Recover(&err)
	defer func() {
		delayQueue.items = nil
		delayQueue.commitAfters = nil
		if err == nil {
			delayQueue.rollbackAfters = nil
		}
	}()
	//
	for i, val := range delayQueue.commitAfters {
		val.handler(val)
		if val.reason != nil {
			err = errorx.Wrap(val.reason, fmt.Sprintf("commit after: index=%v", i))
			return
		}
		// 去掉引用, 避免内存泄漏
		delayQueue.commitAfters[i] = nil
	}
	// 创建需要保存的字典
	if len(delayQueue.items) > 0 {
		saveGroup, hasSaveGroup := saveRoot.saveGroups.Get(delayQueue.saveKey)
		if !hasSaveGroup {
			saveGroup = NewSaveGroup(delayQueue.saveKey)
		} else {
			// 先从saveGroups中删除, 避免修改期间触发保存
			saveRoot.saveGroups.Delete(delayQueue.saveKey)
		}
		for _, dItem := range delayQueue.items {
			//fmt.Printf("dItem %v(%v)=%+v\n", dItem.GetObjectIdent(), len(dItem.Document.DictItems), dItem)
			sItem := saveGroup.GetSaveItem(dItem)
			switch dItem.Opt {
			case Opt_创建:
				if sItem != nil {
					if sItem.Opt == Opt_删除 {
					} else {
						panic("object existed: " + dItem.Ident)
					}
				}
				sItem = saveGroup.NewSaveItem(dItem)
				sItem.Opt = Opt_创建
				if dItem.Data != nil {
					sItem.AddCreateDocument(&OptItem{
						Opt:  DictOpt_创建,
						Path: "/",
						Data: dItem.Data,
					})
				}
				sItem.UpdateByOpts(dItem.OptQueue)
			case Opt_修改:
				if sItem != nil {
					if sItem.Opt == Opt_删除 {
						panic("object deleted: " + dItem.Ident)
					} else {
					}
				} else {
					sItem = saveGroup.NewSaveItem(dItem)
					sItem.Opt = Opt_修改
				}

				if dItem.Data != nil {
					sItem.AddSetDocument(&OptItem{
						Opt:  DictOpt_修改,
						Path: "/",
						Data: dItem.Data,
					})
				}

				sItem.UpdateByOpts(dItem.OptQueue)
			case Opt_删除:
				if sItem != nil { // 如果已经存在保存项，说明未提交数据库
					switch sItem.Opt {
					case Opt_删除: // 已经是删除了，不要处理
					case Opt_创建: // 数据创建但未保存到数据库, 因此直接删除SaveItem, 不会影响最后的数据
						saveGroup.DeleteSaveItem(sItem.Ident)
					case Opt_修改:
						sItem.SetOptDelete()
					}
				} else {
					// 添加删除的保存项
					sItem = saveGroup.GetOrNewSaveItem(dItem)
					sItem.SetOptDelete()
				}
			default:
				// 忽略掉
			}
		}
		if saveGroup != nil {
			saveRoot.saveGroups.Set(saveGroup.key, saveGroup)
		}
	}

	delayQueue.isExpired = true
	return
}

// CommitAfter 提交后执行的方法
// commitAfter意味着数据日记已经落地, 就算崩溃也能还原
// 通常在此处修改内存数据
// Sync最好也放数据修改成功后, 在此处处理, 否则容易使用了还未修正的数据
func (delayQueue *DelayQueue) CommitAfter(handler func(ctx *AfterHandler), args ...interface{}) {
	delayQueue.commitAfters = append(delayQueue.commitAfters, &AfterHandler{
		handler: handler,
		args:    args,
	})
}

func (delayQueue *DelayQueue) Rollback() {
	// 还原数据
	for _, val := range delayQueue.origins {
		val.object.RefUpdate(val.document)
	}

	// 回滚顺序应该是相反的
	for i := len(delayQueue.rollbackAfters) - 1; i >= 0; i-- {
		val := delayQueue.rollbackAfters[i]
		val.handler(val)
		if val.reason != nil {
			panic(errorx.Wrap(val.reason, fmt.Sprintf("rollback after: index=%v", i)))
		}
		// 去掉引用, 避免内存泄漏
		delayQueue.rollbackAfters[i] = nil
	}

	delayQueue.rollbackAfters = delayQueue.rollbackAfters[:0]
}

func (delayQueue *DelayQueue) RollbackAfter(handler func(ctx *AfterHandler), args ...interface{}) {
	delayQueue.rollbackAfters = append(delayQueue.rollbackAfters, &AfterHandler{
		handler: handler,
		args:    args,
	})
}

func (delayQueue *DelayQueue) Transact(f func() (err error)) (err error) {
	err = f()
	if err != nil {
		delayQueue.Rollback()
	} else {
		err = delayQueue.Commit()
		if err != nil {
			delayQueue.Rollback()
			return
		}
	}

	return err
}

// 提交数据，如果失败，则回滚
func (delayQueue *DelayQueue) CommitOrRollback() error {
	err := delayQueue.Commit()
	if err != nil {
		delayQueue.Rollback()
		return err
	}
	return nil
}
